#ifndef NEURALNETWORK
#define NEURALNERWORK

#include <cmath>
#include <vector>
#include <string>

using namespace std;

class NNLayer;
class NNWeight;
class NNNeuron;
class NNConnection;

class NeuralNetwork
{
public:
	NeuralNetwork();
	void Initialize();
	void ForwardPropagate(double* inputVector, int iCount, 
		double* outputVector, int oCount);
	void BackPropagate(double* actualOutput, double* desiredOutput);
	// for checking gradient
	void BackPropagate(double* actualOutput, double* desiredOutput, vector<vector<double>>& prevMomentum,
		vector<vector<double>>& vec_dErr_wrt_dWn, vector<vector<double>>& vec_Weights);
	void GradientChecking(double* inputVector, int iCount, 
		double* actual_label, int oCount, double* desired_label, 
		vector<vector<double>> vec_dErr_wrt_dWn, vector<vector<double>>& vec_Weights);

	vector<NNLayer* > m_Layers;

public:
	double m_LearningRate;
	double m_Momentum;
};

class NNLayer
{
public:
	NNLayer();
	NNLayer( NNLayer* pPrev );
	void Initialize();
	void ForwardPropagate();
	void BackPropagate(vector<double>& dErr_wrt_dXn /* in */, vector<double>& dErr_wrt_dXnm1/* out */, 
		double m_LearningRate);
	// for checking gradient
	void BackPropagate(vector<double>& dErr_wrt_dXn /* in */, vector<double>& dErr_wrt_dXnm1/* out */, 
		double m_LearningRate, double m_Momentum, vector<double>& prevMomentum, vector<vector<double>>& vec_dErr_wrt_dWn, vector<vector<double>>& vec_Weights);

public:
	NNLayer* m_PrevLayer;

	vector<NNNeuron* > m_Neurons;
	vector<NNWeight* > m_Weights;

	vector<NNNeuron* > m_RectNeurons;
	vector<NNNeuron* > m_LCNNeurons;
	vector<NNNeuron* > m_PoolingNeurons;

public:
	double MaxPooling(int a1, int a2, int a3, int a4);
	double AvgPooling(int a1, int a2, int a3, int a4);

public:
	string m_LayerName;
	bool m_NeedPooling;
	bool m_NeedRect;
	bool m_NeedLCN;
	int m_FeatureMapWidth;
	int m_FeatureMapNumber;
};

class NNConnection
{
public:
	NNConnection(unsigned int neuron, unsigned int weight): NeuronIndex(neuron), WeightIndex(weight){};
public:
	unsigned int NeuronIndex, WeightIndex;
};

class NNWeight
{
public:
	NNWeight();
	NNWeight(double val);
	void Initialize();
public:
	double value;
};

class NNNeuron
{
public:
	NNNeuron();
	virtual ~NNNeuron();
	void Initialize();
	void AddConnection(unsigned int iNeuron, unsigned int iWeight);
	void AddConnection(NNConnection const & conn);
public:
	double output;
	vector<NNConnection > m_Connections;
};

#endif